秒杀项目学习笔记 第一、二章——项目框架搭建 实现登陆功能

秒杀项目学习笔记 第一、二章——项目框架搭建 实现登陆功能

redis有多个库,最多16个,默认为0库

第一章:

集成Redis:
1.添加Jedis依赖:
2.添加Fastjson:为了序列化,对象与字符串(json格式)的转化

第二章(实现登陆功能):

1.数据库设计
2.明文密码两次MD5处理
3.JSR303参数检验+全局异常处理器
4.分布式session(重要)

两次MD5(安全)

http是明文传输,用户密码会在网络上传输
1.用户端: PASS = MD5 (明文+固定Salt)
用户端先MD5后再传输给服务端,防止传输窃取
2.服务端: PASS = MD5 (用户输入+ 随机Salt)
接收后,会随机生成salt,与用户md5生成拼装,再做MD5, 结果再写入数据库,放置数据库被盗。防止彩虹表,由一次的MD5反查出密码,所以要再进行一次MD5。

2-2 实现登陆功能

新建了一个LoginVo类
作用:用于在console中输入后台所接收到的mobile和password。
实现:在loginController中引入变量log,使用log.info(loginVo.toString())输出,loginVo就是前端传来的参数
前端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<script>
function login(){
$("#loginForm").validate({
submitHandler:function(form){
doLogin();
}
});
}

function doLogin(){
g_showLoading();//展示loading框

var inputPass = $("#password").val();
var salt = g_passsword_salt; //在common.js中提供
var str = ""+salt.charAt(0)+salt.charAt(2) + inputPass +salt.charAt(5) + salt.charAt(4);
var password = md5(str); //md5.js提供

$.ajax({
url: "/login/do_login",
type: "POST",
data:{
mobile:$("#mobile").val(),
password: password
},
success:function(data){
layer.closeAll(); //不管成功失败,先关框
console.log(data);
if(data.code == 0){
layer.msg("成功");
window.location.href="/goods/to_list";
}else{
layer.msg(data.msg);
}
},
error:function(){
layer.closeAll();
}
});
}
</script>

1
2
3
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static Logger log = LoggerFactory.getLogger(LoginController.class);//导入slf4j的Logger

2-3 JSR303参数校验 + 全局异常处理器

在登陆的时候,传参的时候需要检验。
若每个页面服务都写loginController里的话,很麻烦

参数校验
1.引入spring-boot-starter-validation依赖
2.给前端传来的参数LoginVo加上@Valid注解
3.给参数LoginVo对象类,所需要验证的属性(如电话,密码)加上校验注解,比如NOTNULL,也可以自己创建符合的注解,比如手机号是1开头,共11位。
4.若要新建注解,应在validator文件夹中新建@注解,并传入所对应的验证类。如IsMobileValidator,IsMobileValidator implements ConstraintValidator,重写初始化和验证方法。
5.

异常拦截处理:
问题:当加上参数校验时,若未通过校验,会返回给浏览器400异常,但是并不会显示,添加异常处理显示,这样对用户更加友好
目的:拦截绑定异常,输出错误信息

结构:

Controller类:负责业务的转发,接收传来的@Valid LoginVo(mobile password已装载)

Service类:负责业务逻辑,包含业务上的校验(手机是否存在,密码是否正确)。校验成功返回true,失败则new GlobalException(CodeMsg)对应异常并抛出

GlobalException类:根据CodeMsg构造,具有CodeMsg属性

GlobalExceptionHandler类: 类名前添加注解@ControllerAdvice,类似切面功能,有exceptionHandler方法,能够捕获异常,根据异常类别,返回不同的Result.error(ex.getCodeMsg())

@Valid:负责入参的格式校验,表明LoginVo(mobile password)受校验,可自定义添加注解校验
IsMobileValidator类:用于实现注解@IsMobile(用于验证手机号)的验证,里面可能会使用到工具类ValidatorUtil来校验。

ValidatorUti类:提供了多种验证方法

2-6 分布式Session

分布式多台服务器,处理用户的Session,

可选方法:1.Session同步(应用很少,因为多服务器同步实现复杂)

所用方法:
1.使用工具类UUID,修改并生成不带“-”的cookie字符串String token = UUIDUtil.uuid();

2.将token保存在redis缓存中,以便于下次验证
redisService.set(MiaoshaUserKey.token, token, user); 前缀,key,value

3.将cookie对象加入response,发送回给用户,以便用户下次发送给客户端

1
2
3
4
Cookie cookie = new Cookie(COOKI_NAME_TOKEN, token); //作为name和value
cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
cookie.setPath("/");
response.addCookie(cookie);//加入response,

4.验证Session如何实现??
登陆成功后,在login.html中会有ajax异步window.location.href="/goods/to_list";跳转到商品列表,访问/goods/to_list,客户端会将session放在request中发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    /**
*
* @param model
* @param cookieToken COOKI_NAME_TOKEN是从request中所取的参数名字
* @param paramToken 有时候手机客户端会将token放在参数中传递,而不是cookie中发给客户端,为了兼容加上这个注解,并且优先取paramToken
* @return
*/
@RequestMapping("/to_list")
public String toLogin(Model model,
@CookieValue(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String cookieToken,
@RequestParam(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String paramToken
){
if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
return "login";
}
String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken;//优先取paramToken,为空才取cookieToken
//根据token从redis中获取用户信息
MiaoshaUser user = userService.getByToken(token);
model.addAttribute("user", user);
return "goods_list";
}

}
  1. 最后goods.html通过thymeleaf:<p th:text="'hello:'+${user.nickname}" ></p>

  2. 实现Session的更新功能,根据用户最后一次点击时间为起点,在to_list中调用getByToken获取user对象时,若取到了用户,就会重新addCookie(response, token, user)

分布式Session的优化

在很多的界面跳转时都要验证Session,若在每个方法内都加注解判断token,每个方法都增加根据token获取user的话很冗杂。想到可以将方法抽离出来也需要有没有实现Session更优雅的方式?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RequestMapping("/to_list")
public String list(HttpServletResponse response, Model model,
@CookieValue(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String cookieToken,
@RequestParam(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String paramToken
){
if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
return "login";
}
String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken;//优先取paramToken,为空才取cookieToken
//根据token从redis中获取用户信息
MiaoshaUser user = userService.getByToken(response,token);

model.addAttribute("user", user);

return "goods_list";
}

能不能变成如下方式?直接就获取到了user,不用根据Token来判断了,需要实现argument resolvor参数处理,mvc框架提供了

1
2
3
4
5
@RequestMapping("/to_list")
public String list(Model model, MiaoshaUser user){
model.addAttribute("user", user);
return "goods_list";
}

这里我们联想到了添加参数model,request,response实现的原理————argumentResolver, 通过WebMvcConfigurerAdapter(WebMVC配置适配器)
实现:
1.新建WebConfig继承WebMvcConfigurerAdapter,加上@Configuration

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{

@Autowired
UserArgumentResolver userArgumentResolver;//这是为了添加user实现的resolver

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(userArgumentResolver);//将其加入argumentResolver列表

}
}

2.新建UserArgumentResolver 实现 HandlerMethodArgumentResolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {

@Autowired
MiaoshaUserService userService;

//判断是否是要引入的对应类
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
Class<?> clazz = methodParameter.getParameterType();
return clazz == MiaoshaUser.class;
}

//用于根据各种参数,返回所引入得对象。(就跟引入model一样啦)
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {

HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class);

String paramToken = request.getParameter(MiaoshaUserService.COOKI_NAME_TOKEN);//参数中的根据名字就有
String cookieToken = getCookieValue(request, MiaoshaUserService.COOKI_NAME_TOKEN);//取放在cookies中的cookie,只取cookie名字对上的

if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
return null;
}

String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken;
//根据token从redis中获取用户信息
return userService.getByToken(response,token);
}


private String getCookieValue(HttpServletRequest request, String cookiNameToken) {
//疑问:request.getCookies()会有很多个cookies吗?只取名为MiaoshaUserService.COOKI_NAME_TOKEN的
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if (cookie.getName().equals(cookiNameToken)) {
return cookie.getValue();
}
}
return null;
}
}

![此处输入图片的描述][1]
[1]: http://pbw0qqogs.bkt.clouddn.com/cookie_token.png

这样,我们的GoodsController就变得异常简洁了。能够直接自动的取user,取不到会直接返回个null的user。

1
2
3
4
5
@RequestMapping("/to_list")
public String list(Model model, MiaoshaUser user){
model.addAttribute("user", user);
return "goods_list";
}

Powered by Hexo and Hexo-theme-hiker

Copyright © 2017 - 2019 Jae's blog All Rights Reserved.

UV : | PV :